package com.wq.spi.framework.core;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Enumeration;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;

/**
 * @Author: wangqiang20995
 * @Date:2019/1/24
 * @Description:
 * @Resource:
 */
public class SPIExtensionLoader<T> {

    //spi的类型
    private final Class<?> classWithSpi;

    //spi文件扫描路径
    private static final String SPI_CONFIG_FILE = "META-INF/framework";

    private static final Pattern NAME_SEPARATOR = Pattern.compile("\\s*[,]+\\s");

    //spi扩展点加载器缓存
    private static final Map<Class<?>, SPIExtensionLoader> CACHED_EXTENSIONS
            = new ConcurrentHashMap<>(10);

    //spi扩展点实现类的缓存
    private static final Map<Class<?>, Object> EXTENSION_IMPLEMENTS
            = new ConcurrentHashMap<>(10);

    private String cachedSpiName;

    private Map<String, IllegalStateException> exceptionMap = new ConcurrentHashMap<>();

    private final SPIHolder<Map<String, Class<?>>> mapSpiHolder = new SPIHolder<>();

    //key是spi的name值，可以是方法传入，holder里面的泛型存储的是spi标注的接口对应的具体的实现
    private final Map<String, SPIHolder<Object>> cachedHolder = new ConcurrentHashMap<>();

    private static final String DEFAULT_BOOLEAN_SPIVALUE = "true";


    private SPIExtensionLoader(Class<?> type) {
        this.classWithSpi = type;
    }

    //为某个标有FrameworkSPI注解的接口获取spi加载器
    public static SPIExtensionLoader spiExtensionLoader(Class<?> type) {
        if (type == null) {
            throw new NullPointerException("empty class of spi [" + FrameworkSPI.class.getSimpleName() + "]");
        }

        if (!type.isInterface()) {
            throw new IllegalStateException("class [" + type.getName() + "] is not an interface");
        }

        if (!signerWithAnnotation(FrameworkSPI.class, type)) {
            throw new IllegalStateException("class [" + type.getName() + "] has not an annotation --> [" + FrameworkSPI.class.getSimpleName() + "]");
        }
        SPIExtensionLoader spiExtensionLoader
                = CACHED_EXTENSIONS.get(type);

        if (spiExtensionLoader == null) {
            synchronized (CACHED_EXTENSIONS) {
                if (spiExtensionLoader == null) {
                    spiExtensionLoader = new SPIExtensionLoader(type);
                }
                CACHED_EXTENSIONS.putIfAbsent(type, spiExtensionLoader);
            }
        }

        return spiExtensionLoader;
    }

    //根据spi的配置文件的name获取具体的实现
    public T getExtension(String spiName) {
        if (spiName == null || spiName.trim().length() == 0) {
            throw new NullPointerException("empty spi [" + FrameworkSPI.class.getSimpleName() + "] implement name");
        }

        if (DEFAULT_BOOLEAN_SPIVALUE.equals(spiName)) {
            //获取@FrameworkSPI上的默认值
            return getDefaultSPIValue();
        }

        SPIHolder<Object> holder = cachedHolder.get(spiName);

        if (holder == null) {
            holder = new SPIHolder<>();
            cachedHolder.putIfAbsent(spiName,holder);
        }

        T holderSpi = (T) holder.getValue();

        if (holderSpi == null) {
            synchronized (holder) {
                holderSpi = (T) holder.getValue();
                if (holderSpi == null) {
                    holderSpi = createExtensionObject(spiName);
                }
                holder.setValue(holderSpi);
            }
        }

        return holderSpi;

    }

    private T createExtensionObject(String name) {
        Class<T> extension = (Class<T>) getExtensionClassFromHolder().get(name);
        if (extension == null) {
            throw new RuntimeException("[" + name + "] 的实现类找不到");
        }

        T imple = (T) EXTENSION_IMPLEMENTS.get(extension);

        if (imple == null) {
            try {
                imple = extension.newInstance();
            } catch (Exception e) {
                throw new IllegalStateException("do default constructor for class [" + extension.getName() + "] exception," +
                        "and please check does it has none arguments constructor");
            }
        }

        return imple;
    }

    private T getDefaultSPIValue() {
        //先获取扩展的class
        getExtensionClassFromHolder();

        if (this.cachedSpiName == null || this.cachedSpiName.trim().length() == 0 ||
                DEFAULT_BOOLEAN_SPIVALUE.equals(this.cachedSpiName)) {
            throw new IllegalStateException("default spi implement is empty");
        }

        return getExtension(this.cachedSpiName);
    }

    private Map<String, Class<?>> getExtensionClassFromHolder() {
        //holder其实就是一个对任意一个value的封装
        //map的key代表spi的name，可以是传入的，class代表spi标注的接口的具体实现类的全限定名的class
        Map<String, Class<?>> map = mapSpiHolder.getValue();
        try {
            if (map == null) {
                synchronized (mapSpiHolder) {
                    if (map == null) {
                        map = loadSpiClass();
                        mapSpiHolder.setValue(map);
                    }
                }
            }

        } catch (ClassNotFoundException e) {
            System.out.println("类加载器找不到类：" + e.getMessage());
        }
        return map;
    }

    private Map<String, Class<?>> loadSpiClass() throws ClassNotFoundException {
        final FrameworkSPI frameworkSPI = classWithSpi.getAnnotation(FrameworkSPI.class);
        if (frameworkSPI != null) {
            //看看这个注解上有没有值，有的话直接赋值给默认属性项
            String value = frameworkSPI.value();
            if (value != null && value.trim().length() > 0) {
                value = value.trim();
                String[] targets = NAME_SEPARATOR.split(value);
                if (targets.length > 1) {
                    throw new IllegalStateException("multi value of interface [" + classWithSpi.getName() + "] with spi [" + FrameworkSPI.class.getSimpleName() + "]" +
                            " and only one value is ok");
                }

                this.cachedSpiName = targets[0];
            }
        }

        Map<String, Class<?>> spiExtensionClasses = new ConcurrentHashMap<>();
        loadExtensionClassesFromJar(spiExtensionClasses, SPI_CONFIG_FILE);

        return spiExtensionClasses;
    }

    private void loadExtensionClassesFromJar(Map<String, Class<?>> map, String sourceFilePath) throws ClassNotFoundException {
        //加载指定路径下面的文件
        String fileName = sourceFilePath + File.separator + classWithSpi.getName();

        ClassLoader classLoader = FrameworkSPI.class.getClassLoader();

//        Enumeration<URL> urlEnumeration;
        try {
            if (classLoader == null) {
                classLoader = this.getClass().getClassLoader();
            }

//            urlEnumeration = classLoader.getResources(fileName);
//            while (urlEnumeration.hasMoreElements()) {
//                URL url = urlEnumeration.nextElement();
                InputStream inputStream = classLoader.getResourceAsStream(fileName);
                Properties properties = new Properties();

                try {
                    properties.load(inputStream);
                } catch (IOException e) {
                    System.out.println("打开文件[" + fileName + "]异常");
                } finally {
                    if (inputStream != null) {
                        inputStream.close();
                    }
                }

                instanceSpiClass(properties, map);
//            }
        } catch (IOException e) {
            System.out.println("文件不存在：" + fileName);
        }

    }

    //遍历配置文件，并且实例化，将其放入缓存
    public void instanceSpiClass(Properties properties, Map<String, Class<?>> specificSpiClass) throws ClassNotFoundException {

        for (Object key : properties.keySet()) {
            String spiKey = String.valueOf(key);
            String clazz = properties.getProperty(spiKey);

            Class<?> spiClass = Class.forName(clazz);
            if (!classWithSpi.isAssignableFrom(spiClass)) {
                throw new IllegalStateException("config class [" + clazz + "] is not an implement of interface [" + classWithSpi.getName() + "]");
            }
            specificSpiClass.putIfAbsent(spiKey, spiClass);
        }
    }

    private static boolean signerWithAnnotation(Class<?> annotation, Class clazz) {
        return !(clazz.getAnnotation(annotation) == null);
    }
}
